#include <Game/Include/GameLogic.h>

#include <Comm/Include/AdmComm.h>

#include <Render/Include/RendererDX9.h>

#include <Model/Include/Block.h>

#include <Game/Include/ClientLogic.h>
#include <Game/Include/ServerLogic.h>

#include <sstream>

GameLogic::GameLogic(IWinWindow* window)
{
    this->is_server = true;
    this->comm_channel = NULL;
    this->events_to_process_queue = NULL;
    this->distribution_logic = NULL;
    this->server_wait_for_client_connection = NULL;
     
    this->player_one = NULL;
    this->player_two = NULL;

    this->window = window;
    this->renderer = new RendererDX9(window);
    this->view_in_3D = false;
    this->renderer->render2D();

    this->scene = NULL;

    this->main_task = NULL;
    
    AdmComm::Initialize();
}


GameLogic::~GameLogic(void)
{
    AdmComm::Release();
    delete this->renderer;
    delete this->server_wait_for_client_connection;
}

bool GameLogic::init_comm()
{
    AdmComm* adm_comm = AdmComm::getInstance();
    this->is_server = true;
    bool succesful = true;
    try 
    {
        this->server_wait_for_client_connection = new Semaphore();
        adm_comm->openTCPPassiveConnection(APP_PORT_NUMBER,callbackChannelConected, this);
        this->server_wait_for_client_connection->wait(SERVER_WAIT_TIME_MS);

        this->events_to_process_queue = new SyncQueue();
        this->distribution_logic = new ServerLogic(this->comm_channel, this->events_to_process_queue, CLOCK_TIME_STEP_MS);    
    }
    catch(Semaphore::SemaphoreExceptionType e)
    {
        // it is a timeout exception
        succesful = false;
    }
    
    adm_comm->closeTCPPassiveConnection(APP_PORT_NUMBER);
    delete this->server_wait_for_client_connection;
    this->server_wait_for_client_connection = NULL;

    return succesful;
}

bool GameLogic::init_comm(std::string server_player1_ip)
{
    AdmComm* adm_comm = AdmComm::getInstance();
    bool succesful = true;
    this->is_server = false;
    try
    {
        this->comm_channel = adm_comm->TCPconnect(server_player1_ip.c_str(), APP_PORT_NUMBER);
        this->events_to_process_queue = new SyncQueue();
        this->distribution_logic = new ClientLogic(this->comm_channel, this->events_to_process_queue);
    }
    catch (ChannelExceptionType e)
    {
        // connection failed exception
        succesful = false;
    }
    
    return succesful;
}

void GameLogic::callbackChannelConected(Channel* canal, char* remote_IP, void* user_data)
{
    GameLogic* game_logic = reinterpret_cast<GameLogic*>(user_data);
    game_logic->comm_channel = canal;
    game_logic->server_wait_for_client_connection->signal();
}

void GameLogic::load()
{
    if (this->is_server)
    {
        this->loadScene();
    }
    this->load_finished = false;
    while (!load_finished)
    {
        this->processEvents();
    }    
}

void GameLogic::loadScene()
{
    CreateSceneGameEvent* create_scene_event = new CreateSceneGameEvent(0.0, 50.0, 0.0, 50.0);
    this->distribution_logic->processGameEvent(create_scene_event);
    delete create_scene_event;

    for( int x = 25; x < 46; x+=5 )
    {
        for (int y = 10; y < 45 ; y+=8 )
        {
            InstallBlockGameEvent* install_block_event = new InstallBlockGameEvent(x, y, 7, 4);
            this->distribution_logic->processGameEvent(install_block_event);
            delete install_block_event;
        }
    }

    InstallPlayerGameEvent* install_player_event = new InstallPlayerGameEvent(1, 5.0f, 10.0f);
    this->distribution_logic->processGameEvent(install_player_event);
    delete install_player_event;

    install_player_event = new InstallPlayerGameEvent(2, 5.0f, 40.0f);
    this->distribution_logic->processGameEvent(install_player_event);
    delete install_player_event;

    LoadFinishedGameEvent* finished_load_event = new LoadFinishedGameEvent();
    this->distribution_logic->processGameEvent(finished_load_event);
    delete finished_load_event;
}

void GameLogic::run()
{
    this->main_task = new Task(main_task_function, this, T_TaskPriority::TASK_PRIORITY_HIGH, TASK_SLEEP_TIME_NULL );
    this->main_task->start();
    if(this->is_server)
    {
        ((ServerLogic*)this->distribution_logic)->startClock();
    }
}

void GameLogic::unload()
{
    if(this->is_server)
    {
        ((ServerLogic*)this->distribution_logic)->stopClock();
    }
    delete this->player_one;
    delete this->player_two;
    delete this->scene;
    this->player_one = NULL;
    this->player_two = NULL;
    this->scene = NULL;
}

void GameLogic::finish_comm()
{
    delete this->distribution_logic;
    
    this->events_to_process_queue->closeAndDiscardAll();
    delete this->events_to_process_queue;

    AdmComm* adm_comm = AdmComm::getInstance();
    adm_comm->closeChannel(this->comm_channel);

}

void GameLogic::changeView()
{
    this->view_in_3D = !view_in_3D;
    if(this->view_in_3D)
    {
        this->renderer->render3D();
    }
    else
    {
        this->renderer->render2D();
    }
}

void GameLogic::main_task_function(void* user_data)
{
    GameLogic* game = (GameLogic*)user_data;
    if (!game->hasTheGameFinished())
    {
        game->processEvents();
        game->addGameText();
        game->renderer->renderScene(game->scene, game->getLocalPlayer()->getPaddle());        
    }
    else
    {
        int score_one  = game->player_one->getScore();
        int score_two  = game->player_two->getScore();
        if ( score_one > score_two)
        {
            game->renderer->addText("PLAYER ONE WIN!!!", game->window->getWidth()/4, game->window->getHeight()/2, 5);
        }
        else if (score_two > score_one)
        {
            game->renderer->addText("PLAYER TWO WIN!!!", game->window->getWidth()/4, game->window->getHeight()/2, 5);
        } 
        else
        {
            game->renderer->addText("NO WINNER, IT'S A TIE", game->window->getWidth()/4, game->window->getHeight()/2, 5);
        }
        game->renderer->renderBlack();
        game->main_task->stop();        
    }    
}

void GameLogic::addGameText()
{
    char auxi[200];
    sprintf(auxi, "Player1 --- Balls: %d   Blocks Taken:%d    Score: %d", this->player_one->getBallsAvailable(), this->player_one->getBlocksTaken(), this->player_one->getScore());
    std::string player1_text = auxi;
    
    this->renderer->addText(player1_text, this->window->getWidth() / 10 , this->window->getHeight() / 10, 5);

    sprintf(auxi, "Player2 --- Balls: %d   Blocks Taken:%d    Score: %d", this->player_two->getBallsAvailable(), this->player_two->getBlocksTaken(), this->player_two->getScore());
    std::string player2_text = auxi;
    this->renderer->addText(player2_text, this->window->getWidth() / 10 , this->window->getHeight() * 2 / 10, 5);
}

void GameLogic::processEvents()
{
    // process an event already ordered
    SimpleMessage* simple_message = NULL;
    try
    {
        simple_message = this->events_to_process_queue->pop();
    }
    catch (SyncQueue::SyncQueueExceptionType e)
    {
    }
    std::stringstream s;
    unsigned int size = simple_message->getData(s);
    delete simple_message;
    GameEvent::GameEventType t = GameEvent::getEventType(s);
    switch (t)
    {
    case GameEvent::GameEventType::TIME_EVENT:
        {
            TimeGameEvent* time_event = new TimeGameEvent(s);
            this->processGameEvent(time_event);
            delete time_event;
        }
        break;
    case GameEvent::GameEventType::CREATE_SCENE_EVENT:
        {
            CreateSceneGameEvent* game_event = new CreateSceneGameEvent(s);
            this->processGameEvent(game_event);
            delete game_event;
        }
        break;
    case GameEvent::GameEventType::INSTALL_BLOCK_EVENT:
        {
            InstallBlockGameEvent* game_event = new InstallBlockGameEvent(s);
            this->processGameEvent(game_event);
            delete game_event;
        }
        break;
    case GameEvent::GameEventType::INSTALL_PLAYER_EVENT:
        {
            InstallPlayerGameEvent* game_event = new InstallPlayerGameEvent(s);
            this->processGameEvent(game_event);
            delete game_event;
        }
        break;
    case GameEvent::GameEventType::LOAD_FINISHED_EVENT:
        {
            LoadFinishedGameEvent* game_event = new LoadFinishedGameEvent(s);
            this->processGameEvent(game_event);
            delete game_event;
        }
        break;
    case GameEvent::GameEventType::PLAYER_INPUT_EVENT:
        {
            PlayerInputGameEvent* game_event = new PlayerInputGameEvent(s);
            this->processGameEvent(game_event);
            delete game_event;

        }
        break;
    default:
        throw -1;    
    }
    
}

void GameLogic::processGameEvent(TimeGameEvent* game_event)
{
    this->scene->move(game_event->getDeltaTimeMs());
    this->checkPlayerResults(this->player_one);
    this->checkPlayerResults(this->player_two);
    this->scene->checkPaddlesSceneCollision();
}

void GameLogic::processGameEvent(CreateSceneGameEvent* game_event)
{
    this->scene = new Scene(game_event->getMinX(), game_event->getMaxX(), game_event->getMinY(), game_event->getMaxY());    
}

void GameLogic::processGameEvent(InstallBlockGameEvent* game_event)
{
    Block* block = new Block(game_event->getPosX(), game_event->getPosY(), game_event->getWidth(), game_event->getHeight());
    this->scene->addBlock(block);    
}

void GameLogic::processGameEvent(InstallPlayerGameEvent* game_event)
{
    Ball* player_ball = new Ball(game_event->getPosX() + PADDLE_HEIGHT + BALL_RADIO, game_event->getPosY(), 0.0f, BALL_VELOCITY, BALL_RADIO);
    scene->addBall(player_ball);

    Paddle* player_paddle = new Paddle(game_event->getPosX(), game_event->getPosY(), PADDLE_WIDTH, PADDLE_HEIGHT);
    scene->addPaddle(player_paddle);
    player_paddle->setVelocity(PADDLE_VELOCITY);

    if ( game_event->getPlayerId() == 1)
    {
        this->player_one = new Player(1, player_ball, player_paddle);
    } 
    else
    {
        if (game_event->getPlayerId() == 2)
        {
            this->player_two = new Player(2, player_ball, player_paddle);
        } 
        else
        {
            throw -1;
        }
    }
    
}

void GameLogic::processGameEvent(LoadFinishedGameEvent* game_event)
{
    this->load_finished = true;
}

void GameLogic::processGameEvent(PlayerInputGameEvent* game_event)
{
    Player* player_action = player_one;
    if( game_event->getPlayerId() == 2)
    {
        player_action = this->player_two;
    }
    switch (game_event->getPlayerInput())
    {
    case PlayerInputGameEvent::PlayerInputType::LEFT_ARROW_PRESSED:
        player_action->moveLeft();
        break;
    case PlayerInputGameEvent::PlayerInputType::LEFT_ARROW_RELEASED:
        player_action->stopMovingLeft();
        break;
    case PlayerInputGameEvent::PlayerInputType::RIGHT_ARROW_PRESSED:
        player_action->moveRight();
        break;
    case PlayerInputGameEvent::PlayerInputType::RIGHT_ARROW_RELEASED:
        player_action->stopMovingRight();
        break;
    }
}

void GameLogic::input(const GameInputType &input_type)
{
    int player_id = 1;
    if (!this->is_server)
    {
        player_id = 2;
    }
    PlayerInputGameEvent::PlayerInputType msg_input_type;
    switch (input_type)
    {
    case GameInputType::LEFT_ARROW_PRESSED:
        msg_input_type = PlayerInputGameEvent::PlayerInputType::LEFT_ARROW_PRESSED;
    	break;
    case GameInputType::LEFT_ARROW_RELEASED:
        msg_input_type = PlayerInputGameEvent::PlayerInputType::LEFT_ARROW_RELEASED;
        break;
    case GameInputType::RIGHT_ARROW_PRESSED:
        msg_input_type = PlayerInputGameEvent::PlayerInputType::RIGHT_ARROW_PRESSED;
        break;
    case GameInputType::RIGHT_ARROW_RELEASED:
        msg_input_type = PlayerInputGameEvent::PlayerInputType::RIGHT_ARROW_RELEASED;
        break;
    }
    PlayerInputGameEvent* game_event = new PlayerInputGameEvent(player_id, msg_input_type);
    this->distribution_logic->processGameEvent(game_event);
    delete game_event;
}

void GameLogic::checkPlayerResults(Player* player)
{
    if (0 < player->getBallsAvailable())
    {
        float blocks_taken_count = this->scene->checkBallBlocksCollision(player->getBall());
        if (blocks_taken_count > 0)
        {
            player->tookBlock();
        }
        this->scene->checkBallPaddlesCollision(player->getBall());
        if ( Scene::BallStatusType::BALL_OUT_OF_SCENE == this->scene->checkBallSceneCollision(player->getBall()) )
        {
            this->playerHaveLostABall(player);
        }
    }    
}

bool GameLogic::hasTheGameFinished()
{
    return ( (!this->scene->hasBlocksLeft()) || ( (0 == this->player_one->getBallsAvailable()) && (0 == this->player_two->getBallsAvailable()) ) );
}

void GameLogic::playerHaveLostABall(Player* player)
{
    Ball* old_ball = player->loseBall();
    this->scene->removeBall(old_ball);
    delete old_ball;
    if ( 0 < player->getBallsAvailable() )
    {
        this->installPlayerANewBall(player);
    }
}

void GameLogic::installPlayerANewBall(Player* player)
{
    Paddle* player_paddle = player->getPaddle();
    Ball* ball = new Ball( player_paddle->getPositionX() + PADDLE_HEIGHT + BALL_RADIO, player_paddle->getPositionY(), 0.0f, BALL_VELOCITY, BALL_RADIO);
    scene->addBall(ball);
    player->setNewBall(ball);
}

Player* GameLogic::getLocalPlayer()
{
    if (this->is_server)
    {
        return this->player_one;
    }
    return this->player_two;
}
